package seismosurfer.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

/**
 * A utility for time related operations.
 *
 */
public class TimeUtil {
    
    /** Default date-time pattern */
    public static final String DEFAULT_PATTERN = "yyyy-MMM-dd, HH:mm:ss";

    /** BC date-time pattern. */
    public static final String BC_PATTERN = "-yyyy-MMM-dd, HH:mm:ss";
    
    /** Default locale. */
    public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
    
    /** Date-time pattern used for parsing. */
    public static final String PARSE_PATTERN = "yyyyyMMddHHmmss";
    
    /** Oracle date-time pattern. */
    public static final String ORCL_PATTERN = "yyyyy-MM-dd HH:mm:ss.S";

    private SimpleDateFormat defaultFormat = new SimpleDateFormat(
            DEFAULT_PATTERN, DEFAULT_LOCALE);

    private SimpleDateFormat bcFormat = new SimpleDateFormat(BC_PATTERN,
            DEFAULT_LOCALE);

    private SimpleDateFormat parseFormat = new SimpleDateFormat(PARSE_PATTERN);

    private SimpleDateFormat orclFormat = new SimpleDateFormat(ORCL_PATTERN);

    private static TimeUtil instance = new TimeUtil();

    protected TimeUtil() {
    }

    private Calendar timeCalendar = Calendar.getInstance();
    
    /**
     * Converts an (Oracle) SQL date and time type to 
     * a long number representation, that holds milliseconds 
     * since 1970.
     * 
     * @param date the Date object that holds the date
     * @param time the Time object that holds the time
     * @return a number which represents the milliseconds 
     *         since 1970.
     */
    public static long oracleToJava(java.sql.Date date, java.sql.Time time) {
        instance.calendar.setTime(date);
        instance.timeCalendar.setTime(time);

        instance.calendar.set(Calendar.HOUR_OF_DAY, instance.timeCalendar
                .get(Calendar.HOUR_OF_DAY));
        instance.calendar.set(Calendar.MINUTE, instance.timeCalendar
                .get(Calendar.MINUTE));
        instance.calendar.set(Calendar.SECOND, instance.timeCalendar
                .get(Calendar.SECOND));

        return instance.calendar.getTimeInMillis();
    }
    
    /**
     * Converts the given text that contains a date
     * in the {@link #ORCL_PATTERN Oracle} format
     * to Java representation (milliseconds since 1970).
     * 
     * @param text the String that contains the date
     * @return a number which represents the milliseconds 
     *         since 1970.
     * @throws ParseException
     */
    public static long oracleToJava(String text) throws ParseException {
        Date date = instance.orclFormat.parse(text);
        instance.calendar.setTime(date);

        if (TimeUtil.isBC(instance.calendar.getTimeInMillis())) {
            instance.calendar.add(Calendar.YEAR, 1);
        }

        return instance.calendar.getTimeInMillis();
    }
    
    /**
     * Converts a date-time in the {@link #PARSE_PATTERN this}
     * format to a Java representation (milliseconds since 1970).
     * 
     * @param text the text that contains the date to be parsed
     * @return a number which represents the milliseconds 
     *         since 1970.
     * @throws ParseException
     */
    public static long toDate(String text) throws ParseException {
        return instance.parseFormat.parse(text).getTime();
    }
    
    /**
     * Converts seconds to milliseconds.
     * 
     * @param sec the number which represents seconds
     * @return milliseconds
     */
    public static long toMillis(long sec) {
        return sec * 1000;
    }
    
    /**
     * Converts a date-time representation
     * from milliseconds to a text format.
     * 
     * @param time the number that represents date-time
     * @return a String that represents date-time
     */
    public static String toText(long time) {

        if (isBC(time)) {
            return instance.bcFormat.format(new Date(time));
        }
        return instance.defaultFormat.format(new Date(time));
    }

    private Calendar calendar = Calendar.getInstance();
    
    /**
     * Determines if a given date-time
     * is B.C. or A.D.
     * 
     * @param time the number that represents date-time
     * @return true if it is B.C.
     */
    public static boolean isBC(long time) {
        instance.calendar.setTimeInMillis(time);
        if (instance.calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
            return true;
        }
        return false;
    }
    
    /**
     * Sets the {@link  java.util.Calendar#ERA  era} 
     * and {@link  java.util.Calendar#YEAR  year} fields
     * to the given Calendar according to the given year.  
     * 
     * @param cal the Calendar to be changed
     * @param year the number that represents the year
     */
    public static void setYear(Calendar cal, int year) {
        if (year < 0) {
            year = -year;
            cal.set(Calendar.ERA, GregorianCalendar.BC);
        } else {
            cal.set(Calendar.ERA, GregorianCalendar.AD);
        }
        cal.set(Calendar.YEAR, year);

    }
    
    /**
     * Extracts the year from the given time. 
     * 
     * @param time the number that represents time
     * @return the year
     */
    public static int getYear(long time) {
        instance.calendar.setTimeInMillis(time);

        int year = instance.calendar.get(Calendar.YEAR);

        if (TimeUtil.isBC(time)) {
            year = -year;
        }
        return year;
    }
    
    /**
     * Extracts the month from the given time. 
     * 
     * @param time the number that represents time
     * @return the month 
     */
    public static int getMonth(long time) {
        instance.calendar.setTimeInMillis(time);
        return instance.calendar.get(Calendar.MONTH);
    }
    
    /**
     * Extracts the day of the year from the given time. 
     * 
     * @param time the number that represents time
     * @return the day of the year 
     */
    public static int getDayOfYear(long time) {
        instance.calendar.setTimeInMillis(time);
        return instance.calendar.get(Calendar.DAY_OF_YEAR);
    }
    
    /**
     * Extracts the hour of the day from the given time. 
     * 
     * @param time the number that represents time
     * @return the hour of the day 
     */
    public static int getHourOfDay(long time) {
        instance.calendar.setTimeInMillis(time);
        return instance.calendar.get(Calendar.HOUR_OF_DAY);
    }
    
    /**
     * Converts milliseconds to seconds.
     * 
     * @param millis the number which represents milliseconds
     * @return seconds
     */
    public static double toSeconds(long millis) {
        return (double) millis / 1000;
    }
    
    /**
     * Converts milliseconds to minutes.
     * 
     * @param millis the number which represents milliseconds
     * @return minutes
     */
    public static double toMinutes(long millis) {
        return (double) toSeconds(millis) / (60);
    }
    
    /**
     * Converts milliseconds to hours.
     * 
     * @param millis the number which represents milliseconds
     * @return hours
     */
    public static double toHours(long millis) {
        return (double) toMinutes(millis) / (60);
    }
    
    /**
     * Converts milliseconds to days.
     * 
     * @param millis the number which represents milliseconds
     * @return days
     */
    public static double toDays(long millis) {
        return (double) toHours(millis) / (24);
    }
    
    /**
     * Converts milliseconds to months.
     * 
     * @param millis the number which represents milliseconds
     * @return months
     */
    public static double toMonths(long millis) {
        return (double) toDays(millis) / (31);
    }
    
    /**
     * Converts milliseconds to years.
     * 
     * @param millis the number which represents milliseconds
     * @return years
     */
    public static double toYears(long millis) {
        return (double) toMonths(millis) / (12);
    }
    
    /**
     * Converts milliseconds to centuries.
     * 
     * @param millis the number which represents milliseconds
     * @return centuries
     */
    public static double toCenturies(long millis) {
        return (double) toYears(millis) / (100);
    }

}
